﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using GeoRef.HttpUtils;
using GeoRef.Exceptions;
using System.IO;


namespace GeoRef.GeoQueries
{
    /// <summary>
    /// Encapsulates reverse geocoding query
    /// </summary>
    class ReverseGeoCodingQuery
    {
        #region ReverseGeoCodingQuery Fields

        /// <value>
        /// Url address of web service for reverse geocoding
        /// </value>
        private string url;

        /// <value>
        /// Parameter to append at url to specify radius of searched
        /// geographical coordinates
        /// </value>
        private string paramRadius;

        /// <value>
        /// Range wherein search location
        /// </value>
        private double radius;

        #endregion

        #region ReverseGeoCodingQuery Constructor
        
        /// <summary>
        /// Initializes a new instance of reverse
        /// geocoding request
        /// </summary>
        /// <param name="urlReverseGeocoding">
        /// Url of web service where ask reverse geocoding</param>
        public ReverseGeoCodingQuery(string urlReverseGeocoding) {
            url = urlReverseGeocoding;
            Radius = 1;
        }

        #endregion

        #region ReverseGeoCodingQuery Properties

        /// <summary>
        /// Gets or sets the url of web service 
        /// </summary>
        public string Url {
            get { return url; }
            set { url = value; }
        }

        /// <summary>
        /// Gets or sets the searched radius
        /// </summary>
        public double Radius {
            get { return radius; }
            set { 
                radius = value;
                paramRadius = "&r=" + radius+ "m";
            }
        }

        #endregion

        /// <summary>
        /// Tries to retrieve and to convert the given coordinates
        /// in the correspondent string format address
        /// </summary>
        /// <param name="lat">Latitude of searched coordinates</param>
        /// <param name="lng">Longitude of searched coordinates</param>
        /// <param name="calculatedLat">Retrieved latitude as
        /// response of searched coordinates</param>
        /// <param name="calculatedLng">Retrieved longitude as
        /// response of searched coordinates</param>
        /// <returns>The string address of response</returns>
        public string ReverseGeoCode(double lat, double lng, out double calculatedLat, out double calculatedLng) {
            string address;
            StringBuilder finalUrl = new StringBuilder(url);

            /*tests correctness of passed coordinates and composes
             * the url to ask location */
            if ((lat <= 90 && lat >= -90) &&
               (lng <= 180 && lng >= -180)) {                
                finalUrl.Append("ll=");
                finalUrl.Append(string.Format(CultureInfo.InvariantCulture, "{0:G}", lat));
                finalUrl.Append(",");
                finalUrl.Append(string.Format(CultureInfo.InvariantCulture, "{0:G}", lng));
                finalUrl.Append(paramRadius);
                
            } else {
                throw new ArgumentOutOfRangeException();
            }

            XmlDocument response = GeoRef.HttpUtils.HttpRequester.getXmlFromUri(finalUrl.ToString());
            XmlElement root = response.DocumentElement;
            //Checks status of request
            XmlNodeList nodes = root.GetElementsByTagName("Status");
            if (nodes.Item(0).Attributes.GetNamedItem("code").Value.Equals("1")) {//success
                //retrieves coordinates and address
                XmlNodeList child = root.GetElementsByTagName("Addr");
                
                address = FormatAddress(child.Item(0).InnerText);
                child = root.GetElementsByTagName("Loc");
                calculatedLat = Double.Parse(child.Item(0).Attributes[0].Value.ToString(), CultureInfo.InvariantCulture);
                calculatedLng = Double.Parse(child.Item(0).Attributes[1].Value.ToString(), CultureInfo.InvariantCulture);
            } else {//fail
               //set type of exception
                string modeFail = nodes.Item(0).Attributes.GetNamedItem("text").ToString();
                throw new GeoCodingException(modeFail);
            }
            return address;
        }

        /// <summary>
        /// Composed the address in a consistent yahoo geocoder
        /// result
        /// </summary>
        /// <param name="address">The address to reformat</param>
        /// <returns>The reformatted address</returns>
        private string FormatAddress(string address) {
            string[] split = new string[4];
            string[] split2 = new string[3];
            string num;

            split = address.Split(',');
            //Checks street number and if it is found removes it
            //matches something like: 12 - 23 or 12 - 23 / 21 - 35
            Regex streetNum = new Regex("^(\\d+\\s\\-\\s\\d+)(\\s/\\s\\d+\\s\\-\\s\\d+)?(\\s)");
            Match result = streetNum.Match(split.First());
            if (result.Success == true) {
                num = result.ToString().Split(' ').First();
                split[0] = streetNum.Replace(split[0], "");
                split[0] += " " + num;
            }
            split2 = split[2].Trim().Split(' ');
            string formatted = split[0] + ", " ;
            //removes for Italy state the string that stands for regions
            if (split2[split2.Length - 1].Contains("ITA") == true) {
                formatted += split2[1] + " " + split[1] + " " + split2[2];
            } else {
                formatted += split[1] + ", " + split[2];
            }          
            return formatted;
        }
    }
}
